/*
 * Copyright (c) 2014-2015 Luca Agostini, Alessandro Cantini, Alessandro Dignani
 *
 * This file is part of MeteoCal.
 *
 * MeteoCal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.polimi.se2.agostini_cantini_dignani.meteocal.business;

import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.WeatherForecast;
import javafx.util.Pair;
import org.apache.commons.lang3.time.DateUtils;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * This class manages a cache of weather services.
 *
 * @Author Alessandro Cantini
 */
@Singleton
public class WeatherForecastCache {

	private static final Long ACCEPTABLE_TIME = 6 * DateUtils.MILLIS_PER_HOUR;

	private Map<Pair<String, Integer>, Pair<WeatherForecast, Date>> cache;

	/**
	 * Resets the cache.
	 */
	@PostConstruct
	public void init() {
		cache = new HashMap<>();
	}

	/**
	 * Adds a weather forecast to the cache.
	 *
	 * @param place    The place of the forecast
	 * @param date     The date of the forecast
	 * @param forecast The forecast weather
	 */
	public void addForecast(String place, Date date, WeatherForecast forecast) {
		checkValidForecastInfo(place, date);
		if (forecast == null) {
			throw new IllegalArgumentException();
		}

		Pair<String, Integer> key = createKey(place, date);
		Pair<WeatherForecast, Date> value = createValue(forecast);

		cache.put(key, value);

		assert cache.get(key) == value;
	}

	/**
	 * Retrieves the forecast in the cache, if any. If not present, it returns null.
	 *
	 * @param place The place of the forecast
	 * @param date  The date of the forecast
	 * @return the forecast weather
	 */
	public WeatherForecast getForecast(String place, Date date) {
		checkValidForecastInfo(place, date);

		Pair<String, Integer> key = createKey(place, date);
		Pair<WeatherForecast, Date> value = cache.get(key);

		if (value == null) {
			assert !cache.containsKey(key);
			return null;
		}
		WeatherForecast forecast = value.getKey();
		Date creation = value.getValue();

		if (!isAcceptable(creation)) {
			cache.remove(key);
			return null;
		}

		return forecast;
	}

	private boolean isAcceptable(Date creation) {
		Date now = new Date();
		assert !creation.after(now);

		if (now.getTime() - creation.getTime() < ACCEPTABLE_TIME) {
			return true;
		}
		return false;
	}

	private Pair<String, Integer> createKey(String place, Date date) {
		Integer day = getDay(date);
		String p = place.trim().toLowerCase();
		return new Pair<String, Integer>(place, day);
	}

	private Pair<WeatherForecast, Date> createValue(WeatherForecast weather) {
		return new Pair<WeatherForecast, Date>(weather, new Date());
	}

	private int getDay(Date date) {
		Calendar cal = DateUtils.toCalendar(date);
		return cal.get(Calendar.YEAR) * 366 + cal.get(Calendar.DAY_OF_YEAR);
	}

	private void checkValidForecastInfo(String place, Date date) {
		if (place == null || date == null) {
			throw new IllegalArgumentException("Parameters can't be null");
		}
	}
}
